package gizmoball;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.Timer;

/**
 * This is a sample Swing GUI application.
 */
public class Game {

	public static void main(String[] args) {
		ApplicationWindow frame = new ApplicationWindow();

		// the following code realizes the top level application window
		frame.pack();
		frame.setVisible(true);
		frame.setResizable(false);
		frame.setLocationRelativeTo(null);
	}

	/**
	 * Overview: A BouncingBall is a mutable data type. It simulates a rubber
	 * ball bouncing inside a two dimensional box. It also provides methods that
	 * are useful for creating animations of the ball as it moves.
	 */

	// Note the very indirect way control flow works during an animation:
	//
	// (1) We set up an eventListener with a reference to the animationWindow.
	// (2) We set up a timer with a reference to the eventListener.
	// (3) We call timer.start().
	// (4) Every 20 milliseconds the timer calls eventListener.actionPerformed()
	// (5) eventListener.actionPerformed() modifies the logical
	// datastructure (e.g. changes the coordinates of the ball).
	// (6) eventListener.actionPerformed() calls myWindow.repaint.
	// (7) Swing schedules, at some point in the future, a call to
	// myWindow.paint()
	// (8) myWindow.paint() tells various objects to paint
	// themselves on the provided Graphics context.
	//
	// This may seem very complicated, but it makes the coordination of
	// all the various different kinds of user input much easier. For
	// example here is how control flow works when the user presses the
	// mouse button:
	//
	// (1) We set up an eventListener (actually we just use the same
	// eventListener that is being used by the timer.)
	// (2) We register the eventListener with the window using the
	// addMouseListener() method.
	// (3) Every time the mouse button is pressed inside the window the
	// window calls eventListener.mouseClicked().
	// (4) eventListener.mouseClicked() modifies the logical
	// datastructures. (In this example it calls ball.randomBump(), but
	// in other programs it might do something else, including request a
	// repaint operation).
	//

	/**
	 * Overview: an AnimationWindow is an area on the screen in which a bouncing
	 * ball animation occurs. AnimationWindows have two modes: on and off.
	 * During the on mode the ball moves, during the off mode the ball doesn't
	 * move.
	 */

	private enum Mode{
		STOP,RUN,ADD,EDIT,DELETE
	}
	
	private enum BrickCate{
		L,REC,TRA,TRI,EMPTY
	}
	
	public static class AnimationWindow extends JComponent {

		private static final long serialVersionUID = 3257281448464364082L;

		// Controls how often we redraw
		private static int FRAMES_PER_SECOND = 100;
		
		private static int XNUM = 9;
		
		private static int YNUM = 27;

		private AnimationEventListener eventListener;

		private BouncingBall ball;

		private Baffle bff;
		
		private Bricks bri;

		private Timer timer;
		
		private Tunnel tunnel;
		
		private Mode mode;  //0-stop 1-run 2-add 3-edit 4-delete
		
		private Brick brick; //For binding mouse
		
		private BrickCate brickcate;
		
		private boolean isedit;
		
		
	

		/**
		 * @effects initializes this to be in the off mode.
		 */
		public AnimationWindow() {

			super(); // do the standard JPanel setup stuff

			ball = new BouncingBall(this);
			bff = new Baffle(this);
			bri = new Bricks(XNUM,YNUM);
			tunnel = new Tunnel(this);
			brick = new Brick(0,0);
			brickcate = BrickCate.EMPTY;
			
			// this only initializes the timer, we actually start and stop the
			// timer in the setMode() method
			eventListener = new AnimationEventListener();

			// The first parameter is how often (in milliseconds) the timer
			// should call us back.
			timer = new Timer(1000 / FRAMES_PER_SECOND, eventListener);

			mode = Mode.STOP;
		}

		/**
		 * @modifies g
		 * @effects Repaints the Graphics area g. Swing will then send the newly
		 *          painted g to the screen.
		 * @param g
		 *            Graphics context received by either system or app calling
		 *            repaint()
		 */
		@Override
		public void paintComponent(Graphics g) {
			// first repaint the proper background color (controlled by
			// the windowing system)
			// super.paintComponent(g);
			ball.paint(g);
			bri.paint(g);
			bff.paint(g);
			tunnel.paint(g);
			brick.paint(g);
			
		}

		/**
		 * This method is called when the Timer goes off and we need to move and
		 * repaint the ball.
		 * 
		 * @modifies both the ball and the window that this listener owns
		 * @effects causes the ball to move and the window to be updated to show
		 *          the new position of the ball.
		 */
		private void update() {
			if(mode == Mode.RUN)
			{
				bff.rightturn();
				bff.leftturn();
	
				if(ball.move() == false)
				{
					JOptionPane.showMessageDialog(null, "Game over!"); 
					reStart(); 
				}
				bff.move();
				ball.bffBump(bff.collision(ball.getX(), ball.getY(), ball.getRadius()));
				if(ball.Bump(bri.collision(ball.getX(), ball.getY(), ball.getRadius())) == false)
				{
					repaint();
					JOptionPane.showMessageDialog(null, "You Win!"); 
					reStart(); 
				}
				ball.Bump(tunnel.collision(ball.getX(), ball.getY(), ball.getRadius()));
			}
				
			
			// Have Swing tell the AnimationWindow to run its paint()
			// method. One could also call repaint(), but this would
			// repaint the entire window as opposed to only the portion that
			// has changed.
			repaint();

		}
		
		//restart the game
		private void reStart()
		{
			ball = new BouncingBall(this);
			bff = new Baffle(this);
			
			setMode(Mode.STOP);
		}
		
		//change the brick category in add mode
		public void switchBrick()
		{
			switch(this.brickcate){
			case REC:
				brickcate = BrickCate.TRA;
				break;
			case TRA:
				brickcate = BrickCate.TRI;
				break;
			case TRI:
				brickcate = BrickCate.L;
				break;
			case L:
				brickcate = BrickCate.REC;
				break;
			default:
				break;
			}
		}

		/**
		 * @modifies this
		 * @effects Turns the animation on/off.
		 * @param m
		 *            Boolean indicating if animation is on/off
		 */
		public void setMode(Mode m) {

			if (mode == m) {
				// Nothing to do.
				return;
			}

			if (m == Mode.STOP) {
				// we're about to change mode: turn off all the old listeners
				removeMouseListener(eventListener);
				removeMouseMotionListener(eventListener);
				removeKeyListener(eventListener);
				removeMouseWheelListener(eventListener);
			}

			if (mode == Mode.ADD){
				brick = new Brick(0,0);
				brickcate = BrickCate.EMPTY;
			}
			if(mode == Mode.EDIT){
				isedit = false;
			}
			if (mode == Mode.STOP) {
				// the mode is true: turn on the listeners
				addMouseListener(eventListener);
				addMouseMotionListener(eventListener);
				addKeyListener(eventListener);
				addMouseWheelListener(eventListener);
				requestFocus(); // make sure keyboard is directed to us
				timer.start();
			} else if(m == Mode.STOP){
				timer.stop();
			}
			mode = m;
		}

		/**
		 * Overview: AnimationEventListener is an inner class that responds to
		 * all sorts of external events, and provides the required semantic
		 * operations for our particular program. It owns, and sends semantic
		 * actions to the ball and window of the outer class
		 */
		class AnimationEventListener extends MouseAdapter implements
				MouseMotionListener, KeyListener, ActionListener,MouseWheelListener {

			// MouseAdapter gives us empty methods for the MouseListener
			// interface: mouseClicked, mouseEntered, mouseExited, mousePressed,
			// and mouseReleased.

			/**
			 * For this example we only need to override mouseClicked
			 * 
			 * @modifes the ball that this listener owns
			 * @effects causes the ball to be bumped in a random direction
			 * @param e
			 *            Detected MouseEvent
			 */
			@Override
			public void mouseClicked(MouseEvent e) {	
			}
			/**
			 * MouseMotionListener interface Override this method to act on
			 * mouse drag events.
			 * 
			 * @param e
			 *            Detected MouseEvent
			 */
			public void mouseDragged(MouseEvent e) {
				if(mode == Mode.RUN){
					int mx = e.getX();
					bff.move(mx);
				}
			}

			/**
			 * MouseMotionListener interface Override this method to act on
			 * mouse move events.
			 * 
			 * @param e
			 *            Detected MouseEvent
			 */
			public void mouseMoved(MouseEvent e) {
				if(mode == Mode.RUN){
					int mx = e.getX();
					bff.move(mx);
				}
				else if(mode == Mode.ADD){
					if(brickcate == BrickCate.EMPTY){
						brickcate = BrickCate.REC;
						brick = new RecBrick(0,0);
					}
					brick.setBrickx(e.getX());
					brick.setBricky(e.getY());
				}
				else if(mode == Mode.EDIT){
					brick.setBrickx(e.getX());
					brick.setBricky(e.getY());
				}
			}
			
			/**
			 * MouseMotionListener interface Override this method to act on
			 * mouse Wheel events.
			 * 
			 * @param e
			 *            Detected MouseWheelEvent
			 */
			public void mouseWheelMoved(MouseWheelEvent e) {
				if(mode == Mode.ADD || mode == Mode.EDIT)
				{
					if(e.getWheelRotation() == 1){
						brick.large();
					}
					else if(e.getWheelRotation() == -1){
						brick.small();
					}
				}
			}
			/**
			 * MouseMotionListener interface Override this method to act on
			 * mouse Press events.
			 * 
			 * @param e
			 *            Detected MouseEvent
			 */
			public void mousePressed(MouseEvent e) {
				if(mode == Mode.ADD){
					int i = e.getButton();
					if(i == MouseEvent.BUTTON1){
						Brick temp;
						switch(brickcate){
						case REC:
							temp = new RecBrick((RecBrick)brick);
							break;
						case TRA:
							temp = new TraBrick((TraBrick)brick);
							break;
						case TRI:
							temp = new TriBrick((TriBrick)brick);
							break;
						case L:
							temp = new LBrick((LBrick)brick);
							break;
						default:
							temp = new Brick(brick);
							break;
						}
						bri.addBrick(temp);
					}
					else if(i == MouseEvent.BUTTON3){
						if(brick.getProperty() == 0)
							brick.setProperty(1);
						else if(brick.getProperty() == 1)
						{
							switchBrick();
							switch(brickcate){
							case REC:
								brick = new RecBrick(e.getX(), e.getY());
								break;
							case TRA:
								brick = new TraBrick(e.getX(), e.getY());
								break;
							case TRI:
								brick = new TriBrick(e.getX(), e.getY());
								break;
							case L:
								brick = new LBrick(e.getX(), e.getY());
								break;
							default:
								break;
							}
						}
					}
					else if(i == MouseEvent.BUTTON2){
						brick.rotation();
					}

				}
				else if(mode == Mode.RUN){
					int i = e.getButton();
					if(i == MouseEvent.BUTTON1)
						bff.setjudge_left(true);
					else if(i == MouseEvent.BUTTON3)
						bff.setjudge_right(true);
				}
				else if(mode == Mode.EDIT){
					if(isedit == false){
						Brick temp = bri.isOn(e.getX(), e.getY());
						if(temp != null){			
							brick = temp;
							isedit = true;
						}
					}
					else{
						
						int i = e.getButton();
						if(i == MouseEvent.BUTTON1){
							bri.addBrick(brick);
							brick = new Brick(0,0);
							isedit = false;
						}
						else if(i == MouseEvent.BUTTON2){
							brick.rotation();
						}
					}
				}
				else if(mode ==  Mode.DELETE){
					bri.isOn(e.getX(), e.getY());
				}
			}
			
			/**
			 * MouseMotionListener interface Override this method to act on
			 * mouse Press events.
			 * 
			 * @param e
			 *            Detected MouseEvent
			 */
			public void mouseReleased(MouseEvent e) {
				if(mode == Mode.RUN){
					int i = e.getButton();
					if(i == MouseEvent.BUTTON1)
						bff.setjudge_left(false);
					else if(i == MouseEvent.BUTTON3)
						bff.setjudge_right(false);
				}
			}
			
			/**
			 * We implement the KeyListener interface so that we can bump the
			 * ball in a random direction if keys A-J is press.
			 * 
			 * @modifies the ball that this listener owns
			 * @effects causes the ball to be bumped in a random direction but
			 *          only if one of the keys A-J is pressed.
			 * @param e
			 *            Detected Key Press Event
			 */
			public void keyPressed(KeyEvent e) {
				int i = e.getKeyCode();
				if(i==KeyEvent.VK_SPACE)
				{
					bff.setjudge_left(true);
					bff.setjudge_right(true);
				}
			}
			

			
			public void keyReleased(KeyEvent e) {
				int i = e.getKeyCode();
				if(i==KeyEvent.VK_SPACE)
				{
					bff.setjudge_left(false);
					bff.setjudge_right(false);
				}
			}

			/**
			 * Do nothing.
			 * 
			 * @param e
			 *            Detected Key Typed Event
			 */
			public void keyTyped(KeyEvent e) {
			}

			/**
			 * This is the callback for the timer
			 * 
			 * @param e
			 *            ActionEvent generated by timer
			 */
			public void actionPerformed(ActionEvent e) {
				update();
			}
		}
	}

	/**
	 * Overview: An ApplicationWindow is a top level program window that
	 * contains a toolbar and an animation window.
	 */
	private static class ApplicationWindow extends JFrame {

		private static final long serialVersionUID = 3257563992905298229L;

		protected AnimationWindow animationWindow;

		/**
		 * @effects Initializes the application window so that it contains a
		 *          toolbar and an animation window.
		 */
		public ApplicationWindow() {

			// Title bar
			super("Swing Demonstration Program");

			// respond to the window system asking us to quit
			addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					System.exit(0);
				}
			});

			// Create the toolbar.
			JToolBar toolBar = new JToolBar();
			addButtons(toolBar);

			// Create the animation area used for output.
			animationWindow = new AnimationWindow();
			// Put it in a scrollPane, (this makes a border)
			JScrollPane scrollPane = new JScrollPane(animationWindow);

			// Lay out the content pane.
			JPanel contentPane = new JPanel();
			contentPane.setLayout(new BorderLayout());
			contentPane.setPreferredSize(new Dimension(510, 530));
			contentPane.add(toolBar, BorderLayout.NORTH);
			contentPane.add(scrollPane, BorderLayout.CENTER);
			setContentPane(contentPane);
		}

		/**
		 * @modifies toolBar
		 * @effects adds Run, Stop and Quit buttons to toolBar
		 * @param toolBar
		 *            toolbar to add buttons to.
		 */
		protected void addButtons(JToolBar toolBar) {

			JButton button = null;

			button = new JButton("Run");
			button.setToolTipText("Start the animation");
			// when this button is pushed it calls animationWindow.setMode(true)
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					animationWindow.setMode(Mode.RUN);
				}
			});
			toolBar.add(button);

			button = new JButton("Stop");
			button.setToolTipText("Stop the animation");
			// when this button is pushed it calls
			// animationWindow.setMode(false)
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					animationWindow.setMode(Mode.STOP);
				}
			});
			toolBar.add(button);
			
			button = new JButton("Add");
			button.setToolTipText("Add the bricks");
			// when this button is pushed it calls
			// animationWindow.setMode(false)
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					animationWindow.setMode(Mode.ADD);
				}
			});
			toolBar.add(button);
			
			button = new JButton("Edit");
			button.setToolTipText("Edit the bricks");
			// when this button is pushed it calls
			// animationWindow.setMode(false)
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					animationWindow.setMode(Mode.EDIT);
				}
			});
			toolBar.add(button);
			
			button = new JButton("Delete");
			button.setToolTipText("Delete the bricks");
			// when this button is pushed it calls
			// animationWindow.setMode(false)
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					animationWindow.setMode(Mode.DELETE);
				}
			});
			toolBar.add(button);

			button = new JButton("Quit");
			button.setToolTipText("Quit the program");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					System.exit(0);
				}
			});
			toolBar.add(button);
		}
	}
}
